﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using SenetExperimentsRunner.Logic.Experiment;

namespace SenetExperimentsRunner.Logic.Runner
{
    class ExperimentRunner
    {
        public const string gamePath = @".\..\..\..\Debug\AI_Hw2.exe";

        public delegate void CurrentExpCompleteDelegate(double percentage);
        public static event CurrentExpCompleteDelegate m_CurExpCompleteEvent;

        public delegate void TotalExpCompleteDelegate(double percentage);
        public static event TotalExpCompleteDelegate m_TotalExpCompleteEvent;


        // Possible results for players
        // NONE - no game result
        // DRAW, NORMAL_WIN, NORMAL_LOSE - normal end game result
        // WIN - player won because because of a technical lose of another player
        // TIMEOUT - technical lose, player timeout in game/move
        // MOVE_LIMIT - the player has used all of his moves
        // ILLEGAL_MOVE - technical lose, player made an illegal move

        public enum ResType
        {
            NONE,
            DRAW,
            NORMAL_WIN,
            NORMAL_LOSE, 
			WIN,
            TIMEOUT,
            MOVE_LIMIT,
            ILLEGAL_MOVE}
         ;

        public void RunExperiments(IList<Experiment.Experiment> experiments)
        {
            int total = experiments.Count;
            int finished = 0;
            foreach (Experiment.Experiment experiment in experiments)
            {
                RunExperiment(experiment);
                finished++;
                m_TotalExpCompleteEvent(GetPercentage(total, finished));
            }
        }

        private void RunExperiment(Experiment.Experiment experiment)
        {
            int p1Wins = 0;
            int p2Wins = 0;
            // if no player won after M x 2 turns -> draw

            int sumGameTurnsNum = 0;    // to calculate average time of a game
            int sumGameP1Won = 0;       // to calculate average time for p1 to win
            int sumGameP2Won = 0;       // to calculate average time for p2 to win
            
            //double sumticks_expectimax = 0.0;
            //double sumticks_s1_chance_false_minmax_false = 0.0;
            //double sumticks_s1_chance_true_minmax_false = 0.0;
            //double sumticks_s1_chance_false_minmax_true = 0.0;
            //double sumticks_s1_chance_true_minmax_true = 0.0;


            int trials = experiment.ExperimentParams.TrialsNumber;
            for (int i = 0; i < trials; ++i)
            {
                // run a single game
                RunSingleGame(
                    experiment,
                    ref p1Wins,
                    ref p2Wins,
                    ref sumGameTurnsNum,
                    ref sumGameP1Won,
                    ref sumGameP2Won,
                    //ref sumticks_expectimax,
                    //ref sumticks_s1_chance_false_minmax_false,
                    //ref sumticks_s1_chance_true_minmax_false,
                    //ref sumticks_s1_chance_false_minmax_true,
                    //ref sumticks_s1_chance_true_minmax_true,
                    new Random().Next() /* seed = iteration number */);

                // collect single game results


                // fire finished single game event
                m_CurExpCompleteEvent(GetPercentage(trials, i));
            }
            m_CurExpCompleteEvent(GetPercentage(trials, trials));

            // summarize results
            ExperimentResult experimentResult = new ExperimentResult();
            double percentageP1Wins = (100.0*(double) p1Wins)/(double) trials;
            double percentageP2Wins = (100.0 * (double)p2Wins) / (double)trials;
            double percentageDraws = 100.0 - percentageP1Wins - percentageP2Wins;
            double avgTimePerGame = (double) sumGameTurnsNum/(double) trials;
            double avgTimeP1Win = (double) sumGameP1Won/(double) p1Wins;
            double avgTimeP2Win = (double)sumGameP2Won / (double)p2Wins;

            //experimentResult.TicksExpectimax = sumticks_expectimax / (double)trials;
            //experimentResult.TicksS1ChanceFalseEMinmaxTrue = sumticks_s1_chance_false_minmax_true / (double)trials;
            //experimentResult.TicksS1ChanceFalseMinmaxFalse = sumticks_s1_chance_false_minmax_false/(double) trials;
            //experimentResult.TicksS1ChanceTrueEMinmaxTrue = sumticks_s1_chance_true_minmax_true/(double) trials;
            //experimentResult.TicksS1ChanceTrueMinmaxFalse = sumticks_s1_chance_true_minmax_false/(double) trials;
            //experiment.ExperimentResults = experimentResult;
        }

        private void RunSingleGame(
            Experiment.Experiment experiment,
            ref int p1Wins,
            ref int p2Wins,
            ref int sumGameTurnsNum,
            ref int sumGameP1Won,
            ref int sumGameP2Won,
            //ref double sumticks_expectimax,
            //ref double sumticks_s1_chance_false_minmax_false,
            //ref double sumticks_s1_chance_true_minmax_false,
            //ref double sumticks_s1_chance_false_minmax_true,
            //ref double sumticks_s1_chance_true_minmax_true,
            int seed)
        {
            // param 1: numSticks
            // param 2: boardSize
            // param 3: piecesNum
            // param 4: required depth

            #region /// Build Arguments ///
            string[] arguments =
                {
                    //"numSticks", 
                    //"boardSize",
                    //"piecesNum",
                    //"required depth",
                    "-squares", "100",
                    "-pieces", "5",
                    "-extra_safe", "0",
                    "-extra_rebirth", "0",
                    "-sticks", "6",
                    "-seed", "1200",
                    "-moves", "100",
                    "-time", "40",
                    "-p1_type", "0",
                    "-p2_type", "0",
                    "-h1" , "0",
                    "-h2" , "0",
                    "-h3" , "0",
                    "-h4" , "0",
                    "-h5" , "0",
                    "-h6" , "0",
                    "-h7" , "0",
                    "-g1" , "0",
                    "-g2" , "0",
                    "-g3" , "0",
                    "-g4" , "0",
                    "-g5" , "0",
                    "-g6" , "0",
                    "-g7" , "0",
                };
            ExperimentParams experimentParams = experiment.ExperimentParams;
            GameParameters gameParameters = experimentParams.GameParameters;
            int p1Type = (int)experimentParams.Player1Params.PlayerType;
            int p2Type = (int)experimentParams.Player2Params.PlayerType;
            HeuristicsParams p1HeuristicParams = experimentParams.Player1Params.HeuristicsParams;
            HeuristicsParams p2HeuristicParams = experimentParams.Player2Params.HeuristicsParams;

            //arguments[0] = gameParameters.SticksNumber.ToString();
            //arguments[1] = gameParameters.SquaresNumber.ToString();
            //arguments[2] = gameParameters.InitialPlayerPieces.ToString();
            //arguments[3] = gameParameters.RequiredDepth.ToString();

            arguments[1] = gameParameters.SquaresNumber.ToString();
            arguments[3] = gameParameters.InitialPlayerPieces.ToString();
            arguments[5] = gameParameters.AdditionalSafeSquares.ToString();
            arguments[7] = gameParameters.AdditionalRebirthSquares.ToString();
            arguments[9] = gameParameters.SticksNumber.ToString();
            arguments[11] = seed.ToString();
            arguments[13] = gameParameters.PlayerTurnNumber.ToString();
            arguments[15] = gameParameters.SecondsPerTurn.ToString();
            arguments[17] = p1Type.ToString();
            arguments[19] = p2Type.ToString();
            //heuristics parameters
            for (int i = 21, j=0; i <= 33; i = i + 2 , ++j)
            {
                arguments[i] = p1HeuristicParams.Coefficients[j].ToString();
            }
            for (int i = 35, j = 0; i <= 47; i = i + 2, ++j)
            {
                arguments[i] = p2HeuristicParams.Coefficients[j].ToString();
            }            
            StringBuilder argsBuilder = new StringBuilder();
            foreach (string argument in arguments)
            {
                argsBuilder.Append(argument + " ");
            }
            string argumentsAsString = argsBuilder.ToString();

        #endregion
            
            #region /// Run the game ///
            // run the game
            ProcessStartInfo processStartInfo = new ProcessStartInfo();
            processStartInfo.Arguments = argumentsAsString;
            processStartInfo.FileName = gamePath;
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.CreateNoWindow = true;

            // TODO: add code so we can see output in the gui
            // or in some temp file

            Process process = new Process();
            process.StartInfo = processStartInfo;

            process.Start();
            string procOutput = process.StandardOutput.ReadToEnd();
            System.IO.TextWriter tw = new System.IO.StreamWriter(@".\..\..\..\gameLogs\log" + seed + ".txt");
            tw.Write(procOutput);
            tw.Close();

            process.WaitForExit();

            #endregion

            //string gameAndOut = procOutput.LastIndexOf("MARKER_MARKER_MARKER_MARKER");
            int index = procOutput.LastIndexOf("MARKER_MARKER_MARKER_MARKER");

            string[] res = procOutput.Substring(index).Split(':');
            string p1res = res[1].Trim().Substring(0, 1);
            string p2res = res[2].Trim().Substring(0, 1);
            string turnsPlayed = res[3].Trim().Substring(0);
            ResType p1resEnum = (ResType)int.Parse(p1res);
            ResType p2resEnum = (ResType)int.Parse(p2res);
            int turnsPlayedNum = int.Parse(turnsPlayed);

            //string expRes = res[1].Trim();
            //string s1ffRes = res[2].Trim();
            //string s1tfRes = res[3].Trim();
            //string s1ftRes = res[4].Trim();
            //string s1ttRes = res[5].Trim();

            

            //TODO: remove this - this is for experiment to run not interrupted
            //VerifyTechnialLostMessage(p1resEnum, p2resEnum);

            if (p1resEnum == ResType.NORMAL_WIN || p1resEnum == ResType.WIN)
            {
                p1Wins++;
                sumGameP1Won += turnsPlayedNum;
            }
            if (p2resEnum == ResType.NORMAL_WIN || p2resEnum == ResType.WIN)
            {
                p2Wins++;
                sumGameP2Won += turnsPlayedNum;
            }
            sumGameTurnsNum += turnsPlayedNum;

            //sumticks_expectimax += double.Parse(expRes);
            //sumticks_s1_chance_false_minmax_false += double.Parse(s1ffRes);
            //sumticks_s1_chance_false_minmax_true += double.Parse(s1ftRes);
            //sumticks_s1_chance_true_minmax_false += double.Parse(s1tfRes);
            //sumticks_s1_chance_true_minmax_true += double.Parse(s1ttRes);
        }

        private void VerifyTechnialLostMessage(ResType resType1, ResType resType2)
        {
            if (IsTechninalWinLose(resType1))
            {
                string message = String.Format(
                    "player1 = {0}, player2 = {1}",
                    Enum.GetName(typeof (ResType), resType1),
                    Enum.GetName(typeof (ResType), resType2)
                    );
                MessageBox.Show(message, "Technial win/lose");
            }
        }

        private bool IsTechninalWinLose(ResType resType)
        {
            if (resType == ResType.NONE ||
                resType == ResType.DRAW ||
                resType == ResType.NORMAL_WIN ||
                resType == ResType.NORMAL_LOSE)
            {
                return false;
            }
            return true;
        }


        private double GetPercentage(int total, int finished)
        {
            if (total == 0)
            {
                return 100.0;
            }
            return (double)(100.0 * finished) / (double)total;
        }
    }
}
